using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;

using WorkTracker.BusinessObjects;



namespace WorkTracker
{
	/// <summary>
	/// Generates an icon which graphicaly displays work for a day.
	/// </summary>
	public class StatusIconFactory
	{

		private const int _dimension = 16;

		private static SolidBrush _workHoursBrush = new SolidBrush(Color.FromArgb(0, 120, 0));
		private static SolidBrush _nonWorkHoursBrush = new SolidBrush(Color.FromArgb(120, 120, 120));
		private static SolidBrush _breakHoursBrush = new SolidBrush(Color.FromArgb(200, 200, 0));
		private static SolidBrush _overtimeHoursBrush = new SolidBrush(Color.FromArgb(150, 0, 0));


		private static Rectangle _iconRectangle = new Rectangle(0, 0, _dimension, _dimension);
		private static Rectangle _drawRectangle = new Rectangle(1, 1, _dimension-2, _dimension-2);

		/// <summary>
		/// Generate an icon which displays work hours spend for the provided day.
		/// </summary>
		/// <param name="day">Day for which to generate icon.</param>
		/// <param name="user">User.</param>
		/// <returns></returns>
		public static Icon GenerateIcon(DateTime day, User user) {
			List<StatusChange> dayStatuses = DataAccessLayer.GetStatusChanges(user, DateTime.Today, DateTime.Today.AddDays(1));
			if (dayStatuses.Count == 0)
				return Properties.Resources.WorkTracker;

			StatusChange lastStatusChange = dayStatuses[dayStatuses.Count - 1];
			StatusChange guardStatusChange = new StatusChange();
			guardStatusChange.User = lastStatusChange.User;
			guardStatusChange.Status = lastStatusChange.Status;
			guardStatusChange.Date = DateTime.Now;
			dayStatuses.Add(guardStatusChange);
			TimeSpan totalWorkHours = GetTotalWorkHours(user, dayStatuses);

			Icon icon = null;
			using (Bitmap bm = new Bitmap(_dimension, _dimension)) {
				using (Graphics g = Graphics.FromImage((Image)bm)) {
					g.SmoothingMode = SmoothingMode.AntiAlias;
					g.FillRectangle(Brushes.Transparent, _iconRectangle);

					float startAngle = 0;
					for (int i = 0; i < dayStatuses.Count - 1; i++) {
						// ignore guard status
						StatusChange currentStatusChange = dayStatuses[i];
						StatusChange nextStatusChange = dayStatuses[i+1];
						startAngle = DrawPieArc(g, nextStatusChange.Date.Subtract(currentStatusChange.Date), currentStatusChange.Status, startAngle, user, totalWorkHours);
					}
					DrawCurrentTimeLine(g, dayStatuses[dayStatuses.Count - 1].Status, startAngle);
					icon = Icon.FromHandle(bm.GetHicon());
				}
			}
			return icon;			
		}

		private static TimeSpan GetTotalWorkHours(User user, List<StatusChange> statuses) {
			TimeSpan workHours = TimeSpan.FromHours(user.WorkHoursPerDayNoBreak);
			for (int i = 0; i < statuses.Count - 1; i++) {
				// ignore guard status
				StatusChange currentStatusChange = statuses[i];
				StatusChange nextStatusChange = statuses[i + 1];
				if (currentStatusChange.Status != StatusChange.eStatus.Working)
					workHours = workHours.Add(nextStatusChange.Date.Subtract(currentStatusChange.Date));
			}
			return workHours;
		}


		/// <summary>
		/// Draws a line which represents currently spend hours working.
		/// </summary>
		/// <param name="g">Graphics where to draw.</param>
		/// <param name="status">Status (currently unused).</param>
		/// <param name="angle">Angle of the line (0 represents 12 o'clock)</param>
		/// <param name="totalTime">Total work hours.</param>
		private static void DrawCurrentTimeLine(Graphics g, StatusChange.eStatus status, float angle) {
			Point source = new Point(_dimension / 2, _dimension / 2);
			double angleRadian = Math.PI * (angle-90) / 180;
			int targetX = (int)(Math.Cos(angleRadian) * _dimension / 2 + source.X);
			int targetY = (int)(Math.Sin(angleRadian) * _dimension / 2 + source.Y);
			Point target = new Point(targetX, targetY);
			Pen pen = new Pen(Color.Black,1);
			g.DrawLine(pen, source, target);
		}

		/// <summary>
		/// Draws a pie chart for one status change.
		/// </summary>
		/// <param name="g">Graphics where to draw.</param>
		/// <param name="statusLength">How long is the status to draw</param>
		/// <param name="status">Status which to draw.</param>
		/// <param name="angle">Starting angle of the pie (0 represents 12 o'clock)</param>
		/// <param name="user">User for which to draw (to get work hours).</param>
		/// <param name="totalTime">Total work hours.</param>
		/// <returns>New angle.</returns>
		private static float DrawPieArc(Graphics g, TimeSpan statusLength, StatusChange.eStatus status, float startAngle, User user, TimeSpan totalTime) {
			// ignore if we have invalid statuses at the start of the day
			if (startAngle == 0 && status != StatusChange.eStatus.Working) 
				return 0;

			// skip breaks
			//if (status == StatusChange.eStatus.WorkBreak)
			//	return startAngle;

			Brush brush = GetBrush(status);
			float statusAngle = 0;
			float degreePerMinute = (360 / ((float)totalTime.TotalHours * 60));
			statusAngle = degreePerMinute * (float)statusLength.TotalMinutes;
			float additionalAngle = 0;
			if (statusAngle + startAngle > 360) {
				additionalAngle = (statusAngle + startAngle) - 360;
				statusAngle = 360 - startAngle;
			}
			g.FillPie(brush, _drawRectangle, startAngle - 90, statusAngle);
			// fill overtime
			if (additionalAngle > 0) {
				statusAngle += additionalAngle;
				g.FillPie(_overtimeHoursBrush, _iconRectangle, -90, additionalAngle);
			}
			return startAngle + statusAngle;
		}

		/// <summary>
		/// Gets a brush which will be used to draw certain status.
		/// </summary>
		/// <param name="status">Status.</param>
		/// <returns>Brush.</returns>
		private static Brush GetBrush(StatusChange.eStatus status) {
			switch (status) {
				case StatusChange.eStatus.Working:
					return _workHoursBrush;
				case StatusChange.eStatus.WorkBreak:
					return _breakHoursBrush;
				case StatusChange.eStatus.NotWorking:
					return _nonWorkHoursBrush;
				default:
					throw new ArgumentOutOfRangeException("status");
			}
		}

	}
}
